/***********************************************************************************************************************
 *
 * BetterBeansBinding - keeping JavaBeans in sync
 * ==============================================
 *
 * Copyright (C) 2009 by Tidalwave s.a.s. (http://www.tidalwave.it)
 * http://betterbeansbinding.kenai.com
 *
 * This is derived work from BeansBinding: http://beansbinding.dev.java.net
 * BeansBinding is copyrighted (C) by Sun Microsystems, Inc.
 *
 ***********************************************************************************************************************
 *
 * This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General
 * Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option)
 * any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied
 * warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License for more
 * details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to
 * the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 *
 ***********************************************************************************************************************
 *
 * $Id: ListBindingManager.java 60 2009-04-26 20:47:20Z fabriziogiudici $
 *
 **********************************************************************************************************************/
package org.jdesktop.swingbinding.impl;

import org.jdesktop.beansbinding.*;

import org.jdesktop.observablecollections.ObservableList;
import org.jdesktop.observablecollections.ObservableListListener;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


/**
 * @author sky
 * @author Shannon Hickey
 */
public abstract class ListBindingManager implements ObservableListListener {
    private AbstractColumnBinding[] bindings;
    private ReusableBinding reusableBinding;
    private List<?> elements;
    private List<ColumnDescriptionManager> managers;

    public ListBindingManager() {
        bindings = getColBindings();
    }

    private List<ColumnDescriptionManager> createManagers(
        AbstractColumnBinding[] bindings) {
        List<ColumnDescriptionManager> managers = new ArrayList<ColumnDescriptionManager>(bindings.length);

        for (AbstractColumnBinding binding : bindings) {
            managers.add(new ColumnDescriptionManager(binding));
        }

        return managers;
    }

    protected abstract AbstractColumnBinding[] getColBindings();

    public void setElements(List<?> elements, boolean sendAllChanged) {
        if (this.elements != null) {
            if (this.elements instanceof ObservableList) {
                ((ObservableList) this.elements).removeObservableListListener(this);
            }

            if (managers != null) {
                for (ColumnDescriptionManager manager : managers) {
                    manager.stopListening();
                }
            }
        }

        managers = null;
        reusableBinding = null;
        this.elements = (elements == null) ? Collections.emptyList() : elements;

        boolean addListeners = false;

        if (elements instanceof ObservableList) {
            ((ObservableList) elements).addObservableListListener(this);
            addListeners = !((ObservableList) elements).supportsElementPropertyChanged();
        } else if (elements != null) {
            addListeners = true;
        }

        if (bindings.length != 0) {
            reusableBinding = new ReusableBinding(bindings[0]);
        }

        if (addListeners) {
            managers = createManagers(getColBindings());

            for (ColumnDescriptionManager manager : managers) {
                manager.startListening();
            }
        }

        if (sendAllChanged) {
            allChanged();
        }
    }

    public final Object getElement(int index) {
        return elements.get(index);
    }

    public final List<?> getElements() {
        return elements;
    }

    public final int size() {
        return (elements == null) ? 0 : elements.size();
    }

    public final Object valueAt(int row, int column) {
        if (managers != null) {
            // Make sure the necessary listeners have been registered
            for (ColumnDescriptionManager manager : managers) {
                manager.validateBinding(row);
            }
        }

        reusableBinding.setBaseAndSource(bindings[column], elements.get(row));

        Binding.ValueResult result = reusableBinding.getSourceValueForTarget();

        return result.failed() ? null : result.getValue();
    }

    public final int columnCount() {
        return bindings.length;
    }

    public final void listElementsAdded(ObservableList list, int index,
        int length) {
        if (length == 0) {
            return;
        }

        if (managers != null) {
            for (ColumnDescriptionManager manager : managers) {
                manager.add(index, length);
            }
        }

        added(index, length);
    }

    public final void listElementsRemoved(ObservableList list, int index,
        List elements) {
        if (elements.size() == 0) {
            return;
        }

        if (managers != null) {
            for (ColumnDescriptionManager manager : managers) {
                manager.remove(index, elements.size());
            }
        }

        removed(index, elements.size());
    }

    public final void listElementReplaced(ObservableList list, int index,
        Object oldElement) {
        if (managers != null) {
            for (ColumnDescriptionManager manager : managers) {
                manager.replaced(index);
            }
        }

        changed(index);
    }

    public final void listElementPropertyChanged(ObservableList list, int index) {
        changed(index);
    }

    protected abstract void allChanged();

    protected abstract void valueChanged(int row, int column);

    protected abstract void added(int index, int length);

    protected abstract void removed(int index, int length);

    protected abstract void changed(int row);

    private final class ColumnDescriptionManager {
        private final AbstractColumnBinding columnBinding;
        private List<EntryWrapper> wrappers;

        ColumnDescriptionManager(AbstractColumnBinding columnBinding) {
            this.columnBinding = columnBinding;
        }

        public void startListening() {
            int size = elements.size();
            wrappers = new ArrayList<EntryWrapper>(size);

            for (int i = 0; i < size; i++) {
                wrappers.add(null);
            }
        }

        public void stopListening() {
            for (EntryWrapper wrapper : wrappers) {
                if (wrapper != null) {
                    wrapper.stopListening();
                }
            }

            wrappers = null;
        }

        public void validateBinding(int row) {
            if (wrappers.get(row) == null) {
                EntryWrapper wrapper = new EntryWrapper(getElement(row));
                wrappers.set(row, wrapper);
            }
        }

        void wrapperChanged(EntryWrapper wrapper) {
            int row = wrappers.indexOf(wrapper);
            ListBindingManager.this.valueChanged(row, columnBinding.getColumn());
        }

        private void add(int index, int length) {
            for (int i = 0; i < length; i++) {
                wrappers.add(index, null);
            }
        }

        private void remove(int index, int length) {
            while (length-- > 0) {
                EntryWrapper wrapper = wrappers.remove(index);

                if (wrapper != null) {
                    wrapper.stopListening();
                }
            }
        }

        private void replaced(int index) {
            EntryWrapper wrapper = wrappers.get(index);

            if (wrapper != null) {
                wrapper.stopListening();
            }

            wrappers.set(index, null);
        }

        private final class EntryWrapper implements PropertyStateListener {
            private Object source;

            EntryWrapper(Object source) {
                this.source = source;
                columnBinding.getSourceProperty()
                             .addPropertyStateListener(source, this);
            }

            public void stopListening() {
                columnBinding.getSourceProperty()
                             .removePropertyStateListener(source, this);
                source = null;
            }

            public void propertyStateChanged(PropertyStateEvent pse) {
                if (pse.getValueChanged()) {
                    wrapperChanged(this);
                }
            }
        }
    }

    private final class ReusableBinding extends Binding {
        public ReusableBinding(AbstractColumnBinding base) {
            super(null, base.getSourceProperty(), null,
                base.getTargetProperty(), null);
        }

        public void setBaseAndSource(AbstractColumnBinding base, Object source) {
            setSourceProperty(base.getSourceProperty());
            setTargetProperty(base.getTargetProperty());
            setSourceObject(source);
            setConverter(base.getConverter());
            setSourceNullValue(base.getSourceNullValue());

            if (base.isSourceUnreadableValueSet()) {
                setSourceUnreadableValue(base.getSourceUnreadableValue());
            } else {
                unsetSourceUnreadableValue();
            }
        }

        public final void bindImpl() {
        }

        public final void unbindImpl() {
        }
    }
}
